using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using EPiServer.Shell.Gadgets;
using EPiServer.Research.Gadgets.Models;
using EPiServer.Research.Gadgets.QuickWatch;
using EPiServer.Security;
using System.Collections;
using EPiServer.Data.Dynamic;

namespace EPiServer.Research.Gadgets.Controllers
{
    [EPiServer.Shell.Web.CssResource("Content/QuickWatchGadget.css")]
    [EPiServer.Shell.Web.ScriptResource("Scripts/QuickWatch.js")]
    [Gadget()]
    public class QuickWatchController : Controller
    {

        /// <summary>
        /// This is the default action. Shows a code-box and some static watches depending on the settings.
        /// </summary>
        /// <param name="GadgetId"></param>
        /// <returns></returns>
        public ActionResult Index(Guid GadgetId)
        {
            var model = new QuickWatchModel();
            model.Settings = LoadSettings(GadgetId);
            if (!PrincipalInfo.HasAdminAccess) return View("Index", model);

            //If any watches on the watchlist
            //Run regular watches
            if (!string.IsNullOrEmpty(model.Settings.WatchList))
            {
                DynamicCompiler dc = new DynamicCompiler();
                dc.Namespaces = model.Settings.Namespaces.Split(';').Where(s => s.Trim() != string.Empty).ToArray();
                List<ObjectInfo> data = new List<ObjectInfo>();
                RunRegularWatches(model.Settings, data, dc);
                if (dc.Results.NativeCompilerReturnValue != 0)
                {
                    model.Result = dc.Results.Errors;
                }
                else model.Result = data;
            }

            return View("Index", model);
        }


        /// <summary>
        /// This is the recursive method that uses reflection to iterate through the objects and extends a list of ObjectInfo objects.
        /// </summary>
        /// <param name="settings"></param>
        /// <param name="name"></param>
        /// <param name="typename"></param>
        /// <param name="o"></param>
        /// <param name="data"></param>
        /// <param name="Level"></param>
        /// <param name="Path"></param>
        protected void AnalyzeObject(QuickWatchSettings settings, string name, string typename, object o, List<ObjectInfo> data, int Level, string Path)
        {
            int MaxLevel = settings.MaxDepth;
            //Get the Name, Value and Type of all public instance
            ObjectInfo oi = new ObjectInfo() { Name = name, Level = Level, TypeName = typename, Id=data.Count() };
            oi.Path = Path;
            if (o == null) oi.Value = "null"; 
            else oi.Value = o.ToString();

            data.Add(oi);
            if(o==null) return;
            //Loop through child properties
            if (Level < MaxLevel)
            {
                var props = o.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
                foreach (var prop in props)
                {
                    object val = null;
                    try
                    {
                        if (prop.PropertyType.IsArray) val = "[Array]"; 
                        else val=prop.GetValue(o, null);
                    }
                    catch (Exception exc)
                    {
                        val = "{" + exc.Message + "}";
                    }
                    AnalyzeObject(settings, prop.Name, prop.PropertyType.FullName, val, data, Level + 1, oi.Path+"/"+oi.Id);
                }

                //Loop through static props
                if (settings.ShowStatic)
                {
                    var statprops = o.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                    if (statprops.Count() > 0)
                    {
                        ObjectInfo lst = new ObjectInfo() { Name = "[static]", Level = Level + 1, TypeName = string.Empty, Value = string.Empty, Path=oi.Path+"/"+oi.Id.ToString(), Id = data.Count() };
                        data.Add(lst);
                        foreach (var prop in statprops)
                        {
                            object val = null;
                            try
                            {
                                if (prop.PropertyType.IsArray) val = "[Array]";
                                else val = prop.GetValue(null, null);
                            }
                            catch (Exception exc)
                            {
                                val = "{" + exc.Message + "}";
                            }
                            AnalyzeObject(settings, prop.Name, prop.PropertyType.FullName, val, data, Level + 2, lst.Path+"/"+lst.Id.ToString());
                        }
                    }
                }
                
                //Loop through dictionary items
                if (settings.ShowLists)
                {
                    if (o is IDictionary)
                    {
                        ObjectInfo lst = new ObjectInfo() { Name = "[items]", Level = Level + 1, TypeName = string.Empty, Value = string.Empty, Path=oi.Path+"/"+oi.Id.ToString(), Id = data.Count() };
                        data.Add(lst);
                        int cnt = 0;
                        foreach (object k in (o as IDictionary).Keys)
                        {
                            object c = (o as IDictionary)[k];
                            AnalyzeObject(settings, "[" + k.ToString() + "]", (c == null) ? "" : c.GetType().FullName, c, data, Level + 2,  lst.Path+"/"+lst.Id.ToString());
                            cnt++;
                            if (cnt > settings.MaxArraySize) break;
                        }
                    }
                    else if (o is IEnumerable)
                    {
                        //Loop through array items
                        ObjectInfo lst = new ObjectInfo() { Name = "[items]", Level = Level + 1, TypeName = string.Empty, Value = string.Empty, Path=oi.Path+"/"+oi.Id.ToString(), Id = data.Count() };
                        data.Add(lst);
                        int cnt = 0;
                        foreach (object c in (o as IEnumerable))
                        {

                            AnalyzeObject(settings, "[" + cnt.ToString() + "]", (c == null) ? "" : c.GetType().FullName, c, data, Level + 2, lst.Path+"/"+lst.Id.ToString());
                            cnt++;
                            if (cnt > settings.MaxArraySize) break;
                        }
                    }
                }
            }



        }

        /// <summary>
        /// The helper method that runs any static watches set up.
        /// </summary>
        /// <param name="settings"></param>
        /// <param name="data"></param>
        /// <param name="dc"></param>
        protected void RunRegularWatches(QuickWatchSettings settings, List<ObjectInfo> data, DynamicCompiler dc)
        {
            string[] names=settings.WatchList.Split(';').Where(s => s.Trim()!=string.Empty).ToArray();
            dc.CompileCodeToMethod(settings.WatchList, true);
            if (dc.Results.NativeCompilerReturnValue == 0)
            {
                object[] lst = (object[])dc.RunCode();
                for (int i = 0; i < lst.Length; i++)
                {
                    AnalyzeObject(settings, names[i], (lst[i] != null) ? lst[i].GetType().FullName : "", lst[i], data, 0, "/");
                }
            }

        }

        /// <summary>
        /// Main method that runs custom code entered as well as static watches if they are defined.
        /// </summary>
        /// <param name="Code"></param>
        /// <param name="GadgetId"></param>
        /// <returns></returns>
        [AcceptVerbs(HttpVerbs.Post)]
        [ValidateInput(false)]
        public ActionResult RunCode(string Code, Guid GadgetId)
        {
            //Known bug - will try to create "end-tags" to generic statements

            QuickWatchModel qwm = new QuickWatchModel() { Code = Code, Settings=LoadSettings(GadgetId) };
            //Access check
            if (!PrincipalInfo.HasAdminAccess) return View("Index", qwm);
            
            DynamicCompiler dc = new DynamicCompiler();
            
            //Setup namespaces
            dc.Namespaces = qwm.Settings.Namespaces.Split(';').Where(s => s.Trim() != string.Empty).ToArray();

            //Compile
            dc.CompileCodeToMethod(Code,false);

            //Check for errors - if any, list them
            if (dc.Results.NativeCompilerReturnValue != 0)
            {
                qwm.Result = dc.Results.Errors;
                return View("Index", qwm);
            }
            object o = dc.RunCode();
            
            List<ObjectInfo> data = new List<ObjectInfo>();

            //Analyze core object
            AnalyzeObject(qwm.Settings, dc.Name, (o != null) ? o.GetType().FullName : "", o, data, 0, "/");

            //Run regular watches
            if (!string.IsNullOrEmpty(qwm.Settings.WatchList)) RunRegularWatches(qwm.Settings, data, dc);

            qwm.Result = data;
            return View("Index", qwm);
        }

        #region Settings
        protected QuickWatchSettings GetDefaultSetting(Guid gadgetId)
        {
            QuickWatchSettings set = new QuickWatchSettings();
            set.GadgetId = gadgetId;
            set.MaxArraySize = 20;
            set.MaxDepth = 3;
            set.Namespaces = "System;System.Web;System.Linq;System.Collections.Generic;EPiServer;EPiServer.Core;EPiServer.Data.Dynamic";
            set.ShowCodeBox = true;
            set.ShowLists = true;
            set.ShowStatic = true;
            set.WatchList = "";
            set.MultiLineCode = true;
            return set;
        }

        protected QuickWatchSettings LoadSettings(Guid gadgetId)
        {
            DynamicDataStore store = DynamicDataStoreFactory.Instance.GetStore("QuickWatchStore") ?? DynamicDataStoreFactory.Instance.CreateStore("QuickWatchStore", typeof(QuickWatchSettings));
            var results = store.Find<QuickWatchSettings>("GadgetId", gadgetId).FirstOrDefault();
            if (results!=null) return results;
            else return GetDefaultSetting(gadgetId);
        }

        protected void SaveSettings(QuickWatchSettings settings)
        {
            DynamicDataStore store = DynamicDataStoreFactory.Instance.GetStore("QuickWatchStore") ?? DynamicDataStoreFactory.Instance.CreateStore("QuickWatchStore", typeof(QuickWatchSettings));
            store.Save(settings);
        }
        #endregion

        /// <summary>
        /// Action that shows the configuration screen
        /// </summary>
        /// <param name="gadgetId"></param>
        /// <returns></returns>
        [GadgetAction(Text="Edit")]
        public ActionResult Configure(Guid gadgetId)
        {
            return View(LoadSettings(gadgetId));
        }

        /// <summary>
        /// Saves the configuration made to the Dynamic Data Store
        /// </summary>
        /// <param name="Model"></param>
        /// <param name="GadgetId"></param>
        /// <returns></returns>
        public ActionResult StoreConfiguration([Bind]QuickWatchSettings Model, Guid GadgetId)
        {
            //Save values
            var origSet = LoadSettings(GadgetId);
            Model.GadgetId = GadgetId;
            Model.Id = origSet.Id;
            SaveSettings(Model);
            return Index(GadgetId);
        }

    }
}
